<?php

	require_once( "XMLSQL.php" );

	
	/**
	 * Dash - Dash is a site handler
	 *
	 * Master class that controls the goings on for a Dash powered site
	 * htdocs/index.php is where you can see how to use this beast.
	 */
	class Dash
	{
		/**
		 * Definitions for plugin and controller filenames.  Must be camel-cased
		 * files with %name% having only the first letter uppercase
		 */
		private $controller_filenames = Array( "%name%Controller", "Base%name%Controller" );
		private $plugin_filenames     = Array( "%name%Plugin",     "Base%name%Plugin" );
		
		/**
		 * Constructor does a minimum amount of work.  Just saves the config
		 * info as a class variable and turns error logging on, if config says so
		 * @param SimpleXML $config SimpleXML object containing config
		 */
		function __construct( &$config )
		{
			$this->config   =& $config;
			
			$this->templates = $config->templates;
			
			if ( $this->config->directories )
				$this->dir_controllers = $this->config->directories->controllers;
			
			if ( $config->logging )
			{
				// set error_log path
				if ( $config->logging->file )
				{
					ini_set( "error_log", $config->logging->file );
					ini_set( "log_errors", "on" );
				}
				
				// set display of error messages
				ini_set( "display_errors", $config->logging->display );
			}
		}
		
		/**
		 * Executes Dash.  This method actually does the rendering of the requested page.
		 * If requested response is HTML the entire page will be rendered.  Otherwise
		 * only a portion of the page will be rendered and echo as either raw HTML or a JSON object
		 * @param string $controller Controller to use
		 * @param string $method Method to use
		 * @param array $params Parameters to pass to found controller (if necessary to pass)
		 * @param string $requested_response The requested response type (html, json, partial)
		 */
		public function execute( $controller, $method, $params, $requested_response = "html" )
		{
			error_log( "Controller     - $controller" );
			error_log( "Method         - $method" );
			error_log( "Params         - " . implode("', '", array_keys( $params ) ) . " params found" );
			error_log( "Req. Response  - $requested_response" );
			
			// check if a controller exists
			if ( $result = $this->executeController( $controller, $method, $params ) )
			{
				error_log( "Executed $controller.$method" );
			}
			else
			{
				// load smarty instance
				$smarty =& $this->getSmarty();
				
				// set some default smarty values
				if ( $requested_response != "html" ) $smarty->assign( "is_ajax", true );
				
				// set default method for template - if not set
				if ( $method == false || empty( $method ) )
					$method = $this->templates->default_method;
				
				// set default controller - if not set
				if ( $controller == false || empty( $controller ) )
					$controller = $this->templates->default_controller;
				
				$template = $controller . "/" . $method . ".tpl";
				
				// check if a template exists for controller/action
				if ( $smarty->template_exists( $template ) )
				{
					error_log( "Found template $template" );
					
					$result = $smarty->fetch( $template );
				}
				else
				{
					error_log( "404 No page found" );
					
					// no template can be found!!! show error page
					$result = $smarty->fetch( $this->templates->notfound );
				}
			}
			
			error_log( "Echoing out $requested_response" );
			
			switch( $requested_response )
			{
				case "json":
					if ( !is_array( $result ) )
						$result = Array( 
							"status" => "ok", 
							"response" => $result, 
							"namespace" => $controller, 
							"logged_in" => isset( $_SESSION["user"] )
						);
						
					echo encode_json( $result );
					return;
					
				case "partial":
				default:
					echo $result;
					return;
			}
		}
		
		/**
		 * Attempts to execute controller '$controller' using method '$method' and $params
		 * @param string $controller Controller name
		 * @param string $method Method for controller
		 * @param array $params Array of params to pass on
		 * @return mixed false/null on failure
		 */
		public function executeController( $controller, $method, $params )
		{
			if ( ! $class_instance = $this->loadController( $controller ) )
				return false;
			
			return $this->executeClassMethod( $class_instance, $method, $params, true );
		}
		
		/**
		 * Attempts to execute controller '$controller' using method '$method'
		 * Optionally arguments can be passed as executePlugin( $plugin, $method, $arg1[, $arg2[, ...]] )
		 * @param string $controller Controller name
		 * @param string $method Method for controller
		 * @param array $params Array of params to pass on
		 * @return mixed false/null on failure
		 */
		public function executePlugin( $plugin, $method )
		{
			if ( ! $class_instance = $this->loadPlugin( $plugin ) )
				return false;
			
			$params = func_get_args();
			array_shift( $params );
			array_shift( $params );
			
			return $this->executeClassMethod( $class_instance, $method, $params, false );
		}
		
		/**
		 * Generic execution method for a class->method
		 * @param object $class_instance Instance of class to use
		 * @param string $method Name of method
		 * @param array $params Array of parameters
		 * @param boolean $validate_params Whether or not to validate passed parameters
		 * @return result of execution
		 */
		public function executeClassMethod( &$class_instance, $method, $params, $validate_params = true )
		{
			// find the name of the class from instance created
			$class_name = get_class( $class_instance );
			
			// get a reflection instance of the controller
			$class_info = new ReflectionClass( $class_name );
			
			error_log( "Found " . print_r( $params, 1 ) );
			
			error_log( "Checking '$class_name' for method '$method'" );
			
			// Check that the function exists
			if ( ! $class_info->hasMethod( $method ) )
				return false;
			
			$method_ref = new ReflectionMethod( $class_name, $method );			
			
			// Check the parameters 
			$method_params = $method_ref->getParameters();
			
			// validate the call and return an array of callable parameters, in proper order
			if ( $validate_params == true )
				if ( ( $params = $this->validateParams( $params, $method_params ) ) === false )
					return false;
				
			// making the call
			error_log( "making the call" );
		
			// make the call and return the result
			return $method_ref->invokeArgs( $class_instance, $params );
		}
		
		/**
		 * Load a controller and return the new instance, or false if no class found
		 * @param string $controller Controller name
		 * @return instance of controller
		 */
		public function loadController( $controller )
		{
			if ( $controller == "" ) return false;
			
			return $this->loadClass( $plugin, $this->config->directories->controllers, $this->controller_filenames );
		}
		
		/**
		 * Load a plugin and return the new instance, or false if no class found
		 * @param string $plugin Plugin name
		 * @return instance of plugin
		 */
		public function loadPlugin( $plugin )
		{
			return $this->loadClass( $plugin, $this->config->directories->pluginsr, $this->plugin_filenames );
		}
		
		/**
		 * Loads a class based on a name, base directory and array of name templates
		 * @param string $name Name of class to load
		 * @param string $directory Directory to look in
		 * @param array $name_templates Templates to use to figure out the appropriate filename
		 * @return instance of class on success
		 */
		public function loadClass( $name, $directory, $name_templates )
		{
			foreach( $name_templates AS $format )
			{
				$class_name = str_replace( "%name%", ucwords( $name ), $format );
				
				if ( ! class_exists( $class_name ) )
				{
					$file = $directory . $class_name . ".php";
					
					error_log( "Looking for $class_name @ " . $file );
					
					if ( file_exists( $file ) )
					{
						error_log( "Found it" );
						
						require_once( $file );
						
						// found and loaded the plugin
						return new $class_name( $this );
					}
				}
			}
			
			error_log( "Couldn't find '$plugin'" );
			
			return false;
		}
		
		/**
		 * Validates a set of given params against the defined method params
		 * @param array $given_params Given params to validate
		 * @param ReflectionParameter[] $method_params Array of defined method params - from ReflectionMethod
		 * @return array Params in proper order - false on error
		 */
		public function validateParams( $given_params, $method_params )
		{
			$final_params   = Array();
			$missing_params = Array();
		
			if ( ! empty( $method_params ) )
			{
				foreach( $method_params AS $cur_param )
				{
					$param_name = strtolower( $cur_param->getName() );
					
					if ( ! array_key_exists( $param_name, $given_params ) && ! $cur_param->isDefaultValueAvailable() )
					{
						$missing_params[] = $cur_param->getName();
						continue;
					}
					
					$final_params[] = ( array_key_exists( $param_name, $given_params ) ? $given_params[ $param_name ] : $cur_param->getDefaultValue() );
				}
			
				if ( ! empty( $missing_params ) )
				{
					error_log( "Validation failed - '$namespace.$method' missing parameters: " . join( ",", $missing_params ) );
					return false;
				}
			}
			
			return $final_params;
		}
		
		/**
		 * Create a simple instance of Smarty
		 */
		public function createSmarty()
		{
			error_log( "Creating Smarty" );
			
			$smarty_config = $this->config->templates->smarty;
			
			// load Smarty and instantiate with standard directories (set in xml config)
			require_once( $smarty_config->base . "Smarty.class.php" );
	
			$this->smarty = new Smarty();
			
			$this->smarty->template_dir  = $smarty_config->templates;
			$this->smarty->compile_dir   = $smarty_config->compiled;
			$this->smarty->plugins_dir[] = $smarty_config->plugins;
			
			if ( $_SESSION )
				$this->smarty->assign( "_session", $_SESSION );
		}
		
		/**
		 * Return instance of Smarty.  If none exists, create and then return.
		 * @return instance of Smarty
		 */
		public function getSmarty()
		{
			if ( ! isset( $this->smarty ) )
				$this->createSmarty();
			
			return $this->smarty;
		}
		
		/**
		 * Return an instance of the Database.  If none exists, create and then return.
		 * @return instance of Database
		 */
		public function getDatabase()
		{
			if ( !isset( $this->database ) )
			{
				// get the database info and create the connection
				$db_config = $this->config->database;
				
				$database = Database::connect( $db_config->host, $db_config->username, $db_config->password, $db_config->table );
				
				$this->database =& $database;
			}
			
			return $this->database;
		}
	}
	
	function encode_json($data) 
	{
		if ( $data === false ) return 'false';
		if ( $data === true ) return 'true';
		if (!isset($data)) return '""';
		if ($data == "true" || $data == "false" || is_numeric($data)) return $data;
		if (is_string($data)) return '"' . str_replace(array("\r", "\n"), "", addslashes($data)) . '"';
		
		if (is_array($data)) {
			$json = "{ ";
			
			foreach($data as $k => $v) {
				$v = encode_json($v);
				
				$k = addslashes($k);
				
				$json .= "\"$k\": $v,";
			}
			
			$json = substr($json, 0, -1) . "}";
		}
		
		return $json;
	}

?>